package com.zxq.common.middleware.lock;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * 每个需要watchdog管理的redisLock实例都需要一个对应的RedisLockTimer实例
 */
@Slf4j
public class RedisLockTimer implements LockTimer<RedisLock> {

    @Getter
    protected RedisLock lock;

    /**
     * 刷新间隔
     */
    private long renewalIntervalNano;

    /**
     * 如果剩余时间超过renewalStepNano，本次续租时长renewalStepNano
     */
    private long renewalStepNano;
    /**
     * 超时时长（注意不是超时时间点，是时间跨度）
     */
    final protected long expireNano;
    /**
     * 获取到锁的时间点
     */
    final protected long startNano;

    public RedisLockTimer(RedisLock lock, long renewalIntervalNano, long renewalStepNano) {
        this.lock = lock;

        this.renewalIntervalNano = renewalIntervalNano;
        this.renewalStepNano = renewalStepNano;
        this.expireNano = TimeUnit.SECONDS.toNanos(lock.getExpireTime());
        this.startNano = System.nanoTime();
    }

    private int getRefreshSecond() {
        long res = getRemainingNano();

        log.debug("remaining nano is {}, milli is {}", res, TimeUnit.NANOSECONDS.toMillis(res));

        if (res <= (renewalStepNano - renewalIntervalNano)) {
            return -1;
        }

        if (res > renewalStepNano) {
            res = renewalStepNano;
        }

        res = (int) Math.ceil((double)res / 1000000000L);

        if (res == 0) {
            log.debug("force remaining second to 1");

            res = 1;
        }

        log.debug("remaining second is {}", res);

        return (int)res;
    }


    @Override
    public long getRemainingNano() {
        return expireNano - (System.nanoTime() - startNano);
    }


    @Override
    public boolean isExpired() {
        return getRemainingNano() <= 0;
    }

    @Override
    public void run() {
        log.debug("LockTimer start renew.");

        int counter = 3;

        int refreshSec;
        while (counter-- > 0) {
            try {
                refreshSec = getRefreshSecond();

                //skip expired renewal
                if (refreshSec < 0) {
                    return;
                }

                lock.getRedisProcessor().getRedisLockGateway().compareAndSet(lock.getKey(), lock.getUuid(), lock.getUuid(), refreshSec);

                log.debug("renew lock's expire time, lock: {} extend {} second", lock.getName(), refreshSec);

                return;
            } catch (Exception e) {
                log.info("lock.getRedisProcessor().compareAndSet failed. {}", counter, e);
            }
        }

        log.warn("failed to renew the lease of lock: {}", lock.getName());
    }
}